﻿using System;

using TracedReality.RayTracing;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A material.
    /// </summary>
    public class Material
    {
        /// <summary>
        /// How much diffuse reflection and what color.
        /// </summary>
        public Vector diffuse;

        /// <summary>
        /// How much mirror reflection and what color.
        /// </summary>
        public Vector reflective;

        /// <summary>
        /// How much transmissive reflection and what color.
        /// </summary>
        public Vector refractive;

        /// <summary>
        /// The index of refraction of this Material.
        /// </summary>
        public double index;

        public double shininess;

        /// <summary>
        /// Create a Material with the given colors and index of refraction.
        /// </summary>
        /// <param name="d">Diffuse color.</param>
        /// <param name="r">Reflective color.</param>
        /// <param name="t">Transmissive color.</param>
        /// <param name="i">Index of refraction.</param>
        public Material(Vector d, Vector r = null, double s = 100, Vector t = null, double i = 1)
        {
            diffuse = d;

            if (r == null) { reflective = new Vector(); }
            else { reflective = r; }

            if (t == null) { refractive = new Vector(); }
            else { refractive = t; }

            index = i;

            shininess = s;
        }

        public override bool Equals(object o)
        {
            if (o is Material) { return Equals((Material)o); }
            return false;
        }

        public bool Equals(Material m)
        {
            return diffuse.Equals(m.diffuse) &&
                reflective.Equals(m.reflective) &&
                refractive.Equals(m.refractive) &&
                index == m.index;
        }

        public override int GetHashCode()
        {
            // Cheating but oh well
            return ToString().GetHashCode();
        }

        /// <summary>
        /// Test if this Material has a specular component.
        /// </summary>
        /// <returns>True if this Material is specular, false otherwise.</returns>
        public bool isSpecular()
        {
            return reflective.length != 0 || refractive.length != 0;
        }

        /// <summary>
        /// Test if this Material has a reflective component.
        /// </summary>
        /// <returns>True if this Material is reflective, false otherwise.</returns>
        public bool isReflective()
        {
            return reflective.length != 0;
        }

        /// <summary>
        /// Test if this Material has a refractive/transmissive component.
        /// </summary>
        /// <returns>True if the material is transmissive, false otherwise.</returns>
        public bool isRefractive()
        {
            return refractive.length != 0;
        }

        /// <summary>
        /// Get the probability that a Photon of the given power will bounce diffusely.
        /// </summary>
        /// <param name="power">The power of the Photon.</param>
        /// <returns>The probability of a diffuse reflection.</returns>
        public double diffuseProbability(Vector power) { return (power * diffuse).max() / power.max(); }

        /// <summary>
        /// Get the probability that a Photon of the given power will bounce reflectively.
        /// </summary>
        /// <param name="power">The power of the Photon.</param>
        /// <returns>The probability of a mirror reflection.</returns>
        public double reflectiveProbability(Vector power) { return (power * reflective).max() / power.max(); }

        /// <summary>
        /// Get the probability that a Photon of the given power will bounce transmissively.
        /// </summary>
        /// <param name="power">The power of the Photon.</param>
        /// <returns>The probability of a transmissive reflection.</returns>
        public double refractiveProbability(Vector power) { return (power * refractive).max() / power.max(); }

        /// <summary>
        /// Get what color would result from the given Ray, Hit, incident light direction,
        /// and light color.
        /// </summary>
        /// <param name="ray">Incident Ray.</param>
        /// <param name="hit">Hit with intersection info.</param>
        /// <param name="lightDir">Direction to the Light.</param>
        /// <param name="lightCol">Color of the Light.</param>
        /// <returns>The color of this Material at the given Hit with the given lighting conditions.</returns>
        public Vector shade(Ray ray, Hit hit, Vector lightDir, Vector lightCol)
        {
            Vector n = hit.normal;
            Vector e = ray.direction * -1;
            Vector l = lightDir;

            Vector answer = new Vector();

            double dotnl = n.dot(l);
            if (dotnl < 0) { dotnl = 0; }
            answer += lightCol * diffuse * dotnl;

            Vector r = (n * (2 * dotnl)) - l;
            r.normalize();
            double doter = e.dot(r);
            if (doter < 0) { doter = 0; }
            answer += lightCol * reflective * Math.Pow(doter, shininess) * dotnl;
            
            return answer;
        }

        public static Material Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            MappingEntry found = map.Find("diffuse");
            if (found == null) { throw new ArgumentException("No diffuse entry"); }
            Vector diffuse = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("reflective");
            Vector reflective = null;
            double shine = 100;
            if (found != null)
            {
                reflective = Vector.FromSequence((Sequence)found.Value);
                found = map.Find("shininess");
                if (found != null) { shine = double.Parse(found.Value.ToString()); }
            }
            Vector refractive = null;
            double index = 1;
            found = map.Find("refractive");
            if (found != null)
            {
                refractive = Vector.FromSequence((Sequence)found.Value);
                found = map.Find("index");
                if (found == null) { throw new ArgumentException("No index entry"); }
                index = double.Parse(found.Value.ToString());
            }

            return new Material(diffuse, reflective, shine, refractive, index);
        }

        /// <summary>
        /// Gets a string representation of this Material.
        /// </summary>
        /// <returns>A string representing this Material.</returns>
        public override string ToString()
        {
            return "{ diffuse: " + diffuse + ", reflective: " + reflective + ", shininess: " + shininess + ", transparent: " + refractive + ", index: " + index + " }";
        }
    }
}
